
var Matrix4 = function(src) {
    if (src) {
        this.elements = new Float32Array(16);
        let m = this.elements;
        let dm = src.elements;
        for (let index = 0; index < 16; index++) {
            m[index] = dm[index];
        }
    }else{
        this.elements = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]);
    }
  };
  Matrix4.prototype.setIdentity = function() {
    let m = this.elements;
    for (let index = 0; index < m.length; index++) {
        if (index == 0 || index == 5 || index == 10 || index == 15) {
            m[index] = 1;
        }else{
            m[index] = 0;
        }
    }
    return this;
  };
  
  Matrix4.prototype.set = function(src) {
    let m = this.elements;
    let md = src.elements;
    for (let index = 0; index < md.length; index++) {
        m[index] = md[index];
    }
    return this;
  };
  Matrix4.prototype.concat = function(other) {
    let m = this.elements;
    let dm = other.elements;
    if (m == dm) {
        dm = new Float32Array(16);
        for (let index = 0; index < md.length; index++) {
            md[index] = m[index];
        }
    }
    let tmp0,tmp1,tmp2,tmp3;
    let tmpI = 0;
    for (let index = 0; index < 4; index++) {
        tmp0 = m[index]; tmp1 = m[index + 4]; tmp2 = m[index + 8]; tmp3 = m[index+12];
        tmpI = 0;
        m[index] = tmp0*dm[tmpI] + tmp1*dm[1+tmpI] + tmp2*dm[2+tmpI] + tmp3*dm[3+tmpI]; 
        tmpI = 4;
        m[index+tmpI] = tmp0*dm[tmpI] + tmp1*dm[1+tmpI] + tmp2*dm[2+tmpI] + tmp3*dm[3+tmpI]; 
        tmpI = 8;
        m[index+tmpI] = tmp0*dm[tmpI] + tmp1*dm[1+tmpI] + tmp2*dm[2+tmpI] + tmp3*dm[3+tmpI]; 
        tmpI = 12;
        m[index+tmpI] = tmp0*dm[tmpI] + tmp1*dm[1+tmpI] + tmp2*dm[2+tmpI] + tmp3*dm[3+tmpI]; 
    }
    return this;
  };
  Matrix4.prototype.multiply = Matrix4.prototype.concat;

  Matrix4.prototype.multiplyVector3 = function(pos) {
    let m = this.elements;
    let d = pos.elements;
    let tmp = new Vector3();
    let tm = tmp.elements;
    tm[0] = m[0]*d[0]+m[4]*d[1]+m[8]*d[2]+m[12];
    tm[1] = m[1]*d[0]+m[5]*d[1]+m[9]*d[2]+m[13];
    tm[2] = m[2]*d[0]+m[6]*d[1]+m[10]*d[2]+m[14];
    return tmp;
  };

  Matrix4.prototype.multiplyVector4 = function(pos) {
    let m = this.elements;
    let d = pos.elements;
    let tmp = new Vector4();
    let tm = tmp.elements;
    tm[0] = m[0]*d[0]+m[4]*d[1]+m[8]*d[2]+m[12]*d[3];
    tm[1] = m[1]*d[0]+m[5]*d[1]+m[9]*d[2]+m[13]*d[3];
    tm[2] = m[2]*d[0]+m[6]*d[1]+m[10]*d[2]+m[14]*d[3];
    tm[3] = m[3]*d[0]+m[7]*d[1]+m[11]*d[2]+m[15]*d[3];
    return tmp;
  };

  Matrix4.prototype.transpose = function() {
    let m = this.elements;
    let t;
    let exchange = function(mx,x,y){
        t = mx[x];mx[x] = mx[y];mx[y]=t;
    }
    exchange(m,1,4);
    exchange(m,2,8);
    exchange(m,3,12);
    exchange(m,6,9);
    exchange(m,7,13);
    exchange(m,11,14);
    return this;
  };
  Matrix4.prototype.setInverseOf = function(other) {
    
  
    return this;
  };
  Matrix4.prototype.invert = function() {
    return this.setInverseOf(this);
  };

  Matrix4.prototype.setOrtho = function(left, right, bottom, top, near, far) {
    // OrthoMat =  [2/(R-L),0,0,(L+R)/(L-R)]
    //             [0,2/(T-B),0,(T+B)/(B-T)]
    //             [0,0,2/(F-N),(N+F)/(N-F)]
    //             [0,0,0,1]
    let m = this.elements;
    m[0] = 2/(right-left);
    m[1] = 0;
    m[2] = 0;
    m[3] = 0;

    m[4] = 0;
    m[5] = 2/(top-bottom);
    m[6] = 0;
    m[7] = 0;

    m[8] = 0;
    m[9] = 0;
    m[10] = -2/(far-near)
    m[11] = 0;

    m[12] = (left+right)/(left-right);
    m[13] = (top+bottom)/(bottom-top);
    m[14] = (near+far)/(near - far);
    m[15] = 1;
    return this;
  };

  Matrix4.prototype.ortho = function(left, right, bottom, top, near, far) {
    return this.concat(new Matrix4().setOrtho(left, right, bottom, top, near, far));
  };

  Matrix4.prototype.setFrustum = function(left, right, bottom, top, near, far) {
    let m = this.elements;
    // result = [2N/(R-L), 0, 0, 0]
    //         [0, 2N/(T-B), 0, 0]
    //         [0, 0, (F+N)/(F-N), 2FN/(N-F)]
    //         [0, 0, 1, 0]
    m[0] = 2*near/(right - left);
    m[1] = 0;
    m[2] = 0;
    m[3] = 0;

    m[4] = 0;
    m[5] = 2*near/(top-bottom);
    m[6] = 0;
    m[7] = 0;

    m[8] = 0;
    m[9] = 0;
    m[10] = -(far+near)/(far-near);
    m[11] = 1;

    m[12] = 0;
    m[13] = 0;
    m[14] = 2*far*near/(near-far);
    m[15] = 0;
    
    return this;
  };

  Matrix4.prototype.frustum = function(left, right, bottom, top, near, far) {
    return this.concat(new Matrix4().setFrustum(left, right, bottom, top, near, far));
  };

  Matrix4.prototype.setPerspective = function(fovy, aspect, near, far) {
    let m = this.elements;
    // result = [-1/(tan(Fov/2)*Rat),0,0,0]
    //         [0,1/tan(Fov/2),0,0]
    //         [0,0,(F+N)/(F-N),2FN/(N-F)]
    //         [0,0,1,0]
    fovy = Math.PI*fovy/180;
    let tanv = Math.tan(fov/2);
    
    m[0] = -1/(tanv*aspect);
    m[1] = 0;
    m[2] = 0;
    m[3] = 0;

    m[4] = 0;
    m[5] = 1/tanv;
    m[6] = 0;
    m[7] = 0;

    m[8] = 0;
    m[9] = 0;
    m[10] = (far+near)/(far-near);
    m[11] = -1;

    m[12] = 0;
    m[13] = 0;
    m[14] = 2*far*near/(near-far);
    m[15] = 0;
    
    return this;
  };

  Matrix4.prototype.perspective = function(fovy, aspect, near, far) {
    return this.concat(new Matrix4().setPerspective(fovy, aspect, near, far));
  };

  Matrix4.prototype.setScale = function(x, y, z) {
    let m = this.elements;
    let scal = [x,y,z,1];
    for (let index = 0; index < m.length; index++) {
        if (index == 0 || index == 5 || index == 10 || index == 15) {
            m[index] = scal[index%4];
        }else{
            m[index] = 0;
        }
    }
    return this;
  };
  Matrix4.prototype.scale = function(x, y, z) {
    let m = this.elements;
    
    let changeSca = function(mx,startIndex,val){
        for (let index = 0; index < 4; index++) {
            mx[index+startIndex] *= val;
        }
    }
    changeSca(m,0,x);
    changeSca(m,4,y);
    changeSca(m,8,z);
    
    return this;
  };
  Matrix4.prototype.setTranslate = function(x, y, z) {
    let m = this.elements;
    let tran = [x,y,z,1];
    for (let index = 0; index < m.length; index++) {
        if (index == 12 || index == 13 || index == 14 || index == 15) {
            m[index] = tran[index%4];
        }else{
            m[index] = 0;
        }
    }
    return this;
  };

  Matrix4.prototype.translate = function(x, y, z) {

    let m = this.elements;
    m[12] += m[0]*x + m[4]*y + m[8]*z;
    m[13] += m[1]*x + m[5]*y + m[9]*z;
    m[14] += m[2]*x + m[6]*y + m[10]*z;
    m[15] += m[3]*x + m[7]*y + m[11]*z;
    
    return this;
  };

  Matrix4.prototype.setRotate = function(angle, x, y, z) {
    
  
    return this;
  };
  Matrix4.prototype.rotate = function(angle, x, y, z) {
    return this.concat(new Matrix4().setRotate(angle, x, y, z));
  };

  Matrix4.prototype.setLookAt = function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) {
    zA_x = centerX - eyeX;
    zA_y = centerY - eyeY;
    zA_z = centerZ - eyeZ;

    let sqr = 1/Math.sqrt(zA_x*zA_x,zA_y*zA_y,zA_z*zA_z)
    zA_x *= rlf;
    zA_y *= rlf;
    zA_z *= rlf;

    xA_z = zA_y * upZ - zA_z * upY;
    xA_y = zA_z * upX - zA_x * upZ;
    xA_z = zA_x * upY - zA_y * upX;

    let sqr = 1/Math.sqrt(xA_x*xA_x,xA_y*xA_y,xA_z*xA_z)
    xA_x *= rlf;
    xA_y *= rlf;
    xA_z *= rlf;

    yA_x = xA_y * zA_z - xA_z * zA_y;
    yA_y = xA_z * zA_x - xA_x * zA_z;
    yA_z = xA_x * zA_y - xA_y * zA_x;
  
// x y z 0
// x y z 0
// x y z 0
// 0 0 0 1
    e = this.elements;
    e[0] = xA_x;
    e[1] = yA_x;
    e[2] = -zA_x;
    e[3] = 0;
  
    e[4] = xA_y;
    e[5] = yA_y;
    e[6] = -zA_y;
    e[7] = 0;
  
    e[8] = xA_z;
    e[9] = yA_z;
    e[10] = -zA_z;
    e[11] = 0;
  
    e[12] = 0;
    e[13] = 0;
    e[14] = 0;
    e[15] = 1;

    // Translate.
    return this.translate(-eyeX, -eyeY, -eyeZ);
  };

  Matrix4.prototype.lookAt = function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) {
    return this.concat(new Matrix4().setLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ));
  };

  Matrix4.prototype.dropShadow = function(plane, light) {

  
    return this.concat(mat);
  }
  Matrix4.prototype.dropShadowDirectionally = function(normX, normY, normZ, planeX, planeY, planeZ, lightX, lightY, lightZ) {

    return this.dropShadow([normX, normY, normZ, -a], [lightX, lightY, lightZ, 0]);
  };
  
  var Vector3 = function(opt_src) {
    if (src) {
        this.elements = new Float32Array(3);
        let m = this.elements;
        let dm = src.elements;
        for (let index = 0; index < 3; index++) {
            m[index] = dm[index];
        }
    }else{
        this.elements = new Float32Array(3);
    }
  }
  Vector3.prototype.normalize = function() {
    let m = this.elements;
    let sqr = Math.sqrt(m[0]*m[0],m[1]*m[1],m[2]*m[2])
    m[0] *= 1/sqr;
    m[1] *= 1/sqr;
    m[2] *= 1/sqr;
    return this;
  };
  var Vector4 = function(opt_src) {
    if (src) {
        this.elements = new Float32Array(4);
        let m = this.elements;
        let dm = src.elements;
        for (let index = 0; index < 4; index++) {
            m[index] = dm[index];
        }
    }else{
        this.elements = new Float32Array(4);
    }
  }